﻿// Author: Thiago Boufleuhr
// E-mail: boufleuhr@gmail.com

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using BoufleuhrSolutions.DynamicDTO.Attributes;

namespace BoufleuhrSolutions.DynamicDTO
{
    public static class DataTransferHelper
    {
        private static PropertyDescriptorCollection GetTransferProperties(Type typ)
        {
            AssertTransferObject(typ);

            return TypeDescriptor.GetProperties(
                typ,
                new Attribute[] { new DataTransferPropertyAttribute() }
            );
        }

        private static PropertyDescriptorCollection GetTransferProperties(object value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            return GetTransferProperties(value.GetType());
        }

        private static bool IsDataTransferObject(Type typ)
        {
            if (typ == null)
                throw new ArgumentNullException("typ");

            return typ.IsDefined(typeof(DataTransferObjectAttribute), false);
        }

        private static bool IsDataTransferObject(object value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            return IsDataTransferObject(value.GetType());
        }

        private static void AssertTransferObject(Type typ)
        {
            if (!IsDataTransferObject(typ))
                throw new InvalidOperationException("Objeto não está decorado com o atributo DataTransferObject");
        }

        private static void AssertSerializable(object value)
        {
            if (value == null)
                return;

            if (!value.GetType().IsSerializable)
                throw new InvalidOperationException(
                    String.Format("Objeto {0} não esta marcado como serialiável", value.GetType().FullName));
        }

        public static DataTransferObject Generate(object value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            Type valueType = value.GetType();
            AssertTransferObject(valueType);

            DataTransferObject dataTransferObject = new DataTransferObject(valueType);
            foreach (PropertyDescriptor property in GetTransferProperties(valueType))
            {
                //TODO: Validar os tipos de lista, dicionario, todos os tipos de coleção
                //TODO: Validar a propriedade "Setable"

                var transferProperty = BuildTransferProperty(value, property);
                dataTransferObject.DataTransferProperties.Add(transferProperty);
            }

            return dataTransferObject;
        }

        private static DataTransferProperty BuildTransferProperty(object value, PropertyDescriptor property)
        {
            var transferProperty = new DataTransferProperty(property.Name);
            var propertyValue = property.GetValue(value);

            if (propertyValue != null)
            {
                if (IsDataTransferObject(propertyValue))
                {
                    transferProperty.Nested = Generate(propertyValue);
                }
                else
                {
                    AssertSerializable(propertyValue);
                    transferProperty.Value = propertyValue;
                }
            }
            return transferProperty;
        }

        public static object Rebuild(DataTransferObject dataTransferObject)
        {
            if (dataTransferObject == null)
                throw new ArgumentNullException("container");

            Type originalClassType = Type.GetType(dataTransferObject.ClassName);
            AssertTransferObject(originalClassType);

            object instance = Activator.CreateInstance(originalClassType);
            PropertyDescriptorCollection instanceProperties = GetTransferProperties(originalClassType);

            foreach (var transferProperty in dataTransferObject.DataTransferProperties)
            {
                PropertyDescriptor instanceProperty = instanceProperties.Find(transferProperty.Name, true);
                if (instanceProperty == null)
                    continue;

                if (transferProperty.HasNested())
                    instanceProperty.SetValue(instance, Rebuild(transferProperty.Nested));
                else
                    instanceProperty.SetValue(instance, transferProperty.Value);
            }

            return instance;
        }
    }
}
